package online.foxnull2.foxmall.search.service.impl;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import online.foxnull2.common.to.es.SkuEsModel;
import online.foxnull2.foxmall.search.config.ElasticSearchConfig;
import online.foxnull2.foxmall.search.constant.ESConstant;
import online.foxnull2.foxmall.search.service.MySearchService;
import online.foxnull2.foxmall.search.vo.SearchParamVo;
import online.foxnull2.foxmall.search.vo.SearchResult;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Slf4j
@Service
public class MySearchServiceImpl implements MySearchService {

    private final RestHighLevelClient client;

    @Autowired
    public MySearchServiceImpl(RestHighLevelClient client) {
        this.client = client;
    }

    @Override
    public SearchResult search(SearchParamVo param, String queryString) {
        SearchResult result = new SearchResult();
        SearchRequest request = buildSearchRequest(param);
        try {
            SearchResponse response = client.search(request, ElasticSearchConfig.COMMON_OPTIONS);
            result = buildSearchResult(response, param, queryString);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    private SearchResult buildSearchResult(SearchResponse response, SearchParamVo param, String queryString) {
        SearchResult result = new SearchResult();
        SearchHits hits = response.getHits();
        if (!ObjectUtils.isEmpty(hits.getHits())) {
            List<SkuEsModel> products = new ArrayList<>();
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                if (!ObjectUtils.isEmpty(param.getKeyword())) {
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    skuEsModel.setSkuTitle(skuTitle.getFragments()[0].toString());
                }
                products.add(skuEsModel);
            }
            result.setProducts(products);
        }

        Aggregations aggregations = response.getAggregations();
        if (!ObjectUtils.isEmpty(aggregations)) {
            ParsedLongTerms catalog_agg = aggregations.get("catalog_agg");
            if (!ObjectUtils.isEmpty(catalog_agg) && !ObjectUtils.isEmpty(catalog_agg.getBuckets())) {
                List<SearchResult.Category> categoryList = new ArrayList<>();
                for (Terms.Bucket bucket : catalog_agg.getBuckets()) {
                    SearchResult.Category category = new SearchResult.Category();
                    category.setCategoryId(bucket.getKeyAsNumber().longValue());
                    ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
                    category.setCategoryName(catalog_name_agg.getBuckets().get(0).getKeyAsString());
                    categoryList.add(category);
                }
                result.setCategories(categoryList);
            }
            ParsedLongTerms brand_agg = aggregations.get("brand_agg");
            if (!ObjectUtils.isEmpty(brand_agg) && !ObjectUtils.isEmpty(brand_agg.getBuckets())) {
                List<SearchResult.Brand> brandList = new ArrayList<>();
                for (Terms.Bucket bucket : brand_agg.getBuckets()) {
                    SearchResult.Brand brand = new SearchResult.Brand();
                    brand.setBrandId(bucket.getKeyAsNumber().longValue());
                    ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
                    brand.setBrandName(brand_name_agg.getBuckets().get(0).getKeyAsString());
                    ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
                    brand.setBrandImg(brand_img_agg.getBuckets().get(0).getKeyAsString());
                    brandList.add(brand);
                }
                result.setBrands(brandList);
            }
            ParsedNested attrs_agg = aggregations.get("attrs_agg");
            Aggregations attrs_aggAggregations = attrs_agg.getAggregations();
            if (!ObjectUtils.isEmpty(attrs_aggAggregations) && !ObjectUtils.isEmpty(attrs_aggAggregations)) {
                List<SearchResult.Attr> attrList = new ArrayList<>();
                ParsedLongTerms attr_agg = attrs_aggAggregations.get("attr_agg");
                for (Terms.Bucket bucket : attr_agg.getBuckets()) {
                    SearchResult.Attr attr = new SearchResult.Attr();
                    attr.setAttrId(bucket.getKeyAsNumber().longValue());
                    ParsedStringTerms attr_name_agg = bucket.getAggregations().get("attr_name_agg");
                    attr.setAttrName(attr_name_agg.getBuckets().get(0).getKeyAsString());
                    ParsedStringTerms attr_value_agg = bucket.getAggregations().get("attr_value_agg");
                    if (!ObjectUtils.isEmpty(attr_value_agg) && !ObjectUtils.isEmpty(attr_value_agg.getBuckets())) {
                        List<String> attrValue = new ArrayList<>();
                        for (Terms.Bucket attrValueAggBucket : attr_value_agg.getBuckets()) {
                            attrValue.add(attrValueAggBucket.getKeyAsString());
                        }
                        attr.setAttrValue(attrValue);
                    }
                    attrList.add(attr);
                }
                result.setAttrs(attrList);
            }
        }

        Integer pageNum = param.getPageNum();
        result.setPageNum(pageNum);
        long total = hits.getTotalHits().value;
        result.setTotal(total);
        int totalPages = (int) ((total - 1) / ESConstant.PRODUCT_PAGE_SIZE + 1);
        result.setTotalPages(totalPages);

        if (!ObjectUtils.isEmpty(param.getAttrs())) {
            List<SearchResult.Nav> navList = param.getAttrs().stream().map(attr -> {
                String[] strings = attr.split("_");
                SearchResult.Nav nav = new SearchResult.Nav();
                String attrId = strings[0];
                nav.setName(strings[0])
                        .setValue(strings[1]);
                result.getAttrs().forEach(attrRes -> {
                    if (Objects.equals(Long.valueOf(attrId), attrRes.getAttrId())) {
                        nav.setName(attrRes.getAttrName());
                    }
                });
                String encodeStr = URLEncoder.encode(attr, StandardCharsets.UTF_8)
                        .replaceAll("\\+", "%20");
                String regex = "[&]?attrs=" + encodeStr;
                String uri = queryString.replaceFirst(regex, "");
                nav.setLink("http://search.foxmall.com/list.html?" + uri);

                result.getAttrIds().add(Long.valueOf(attrId));
                return nav;
            }).collect(Collectors.toList());
            result.setNavs(navList);
        }
        if (!ObjectUtils.isEmpty(param.getBrandId())) {
            List<SearchResult.Nav> navList = result.getNavs();
            SearchResult.Nav nav = new SearchResult.Nav();
            nav.setName("Brand");
            StringBuffer buffer = new StringBuffer();
            param.getBrandId().forEach(brandId -> {
                result.getBrands().forEach(brand -> {
                    if (Objects.equals(brandId, brand.getBrandId())) {
                        buffer.append(brand.getBrandName());
                    }
                });
                String regex = "[&]?brandId=" + brandId;
                String uri = queryString.replaceFirst(regex, "");
                nav.setLink("http://search.foxmall.com/list.html?" + uri);
            });
            nav.setValue(String.join(":", buffer));
            navList.add(nav);
        }
        log.info("result -> {}", result);
        return result;
    }

    private SearchRequest buildSearchRequest(SearchParamVo param) {
        SearchSourceBuilder source = new SearchSourceBuilder();

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (!ObjectUtils.isEmpty(param.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));

            source.highlighter(new HighlightBuilder().field("skuTitle").preTags("<b style='color:red'>").postTags("</b>"));
        }
        if (!ObjectUtils.isEmpty(param.getCatalog3Id())) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
        }
        if (!ObjectUtils.isEmpty(param.getBrandId())) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
        }
        if (!ObjectUtils.isEmpty(param.getAttrs())) {
            param.getAttrs().forEach(attr -> {
                BoolQueryBuilder boolQueryAttrs = QueryBuilders.boolQuery();
                String[] strings = attr.split("_");
                String attrId = strings[0];
                String[] attrValues = strings[1].split(":");
                boolQueryAttrs.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                boolQueryAttrs.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                boolQuery.filter(QueryBuilders.nestedQuery("attrs", boolQueryAttrs, ScoreMode.None));
            });
        }
        if (!ObjectUtils.isEmpty(param.getHasStock())) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));
        }
        if (!ObjectUtils.isEmpty(param.getSkuPrice())) {
            String[] prices = param.getSkuPrice().split("_", -1);
            if (prices.length == 2) {
                RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
                if (!ObjectUtils.isEmpty(prices[0])) {
                    rangeQuery.gte(prices[0]);
                }
                if (!ObjectUtils.isEmpty(prices[1])) {
                    rangeQuery.lte(prices[1]);
                }
                boolQuery.filter(rangeQuery);
            }
        }
        source.query(boolQuery);

        if (!ObjectUtils.isEmpty(param.getSort())) {
            String[] strings = param.getSort().split(("_"));
            source.sort(strings[0], "desc".equalsIgnoreCase(strings[1]) ? SortOrder.DESC :
                    "asc".equalsIgnoreCase(strings[1]) ? SortOrder.ASC :
                            SortOrder.DESC);
        }
        source.from((param.getPageNum() - 1) * ESConstant.PRODUCT_PAGE_SIZE);
        source.size(ESConstant.PRODUCT_PAGE_SIZE);

        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg").field("brandId");
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName"));
        brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg"));
        source.aggregation(brand_agg);
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId");
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName"));
        source.aggregation(catalog_agg);
        NestedAggregationBuilder attrs_agg = AggregationBuilders.nested("attrs_agg", "attrs");
        TermsAggregationBuilder attr_agg = AggregationBuilders.terms("attr_agg").field("attrs.attrId");
        attr_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName"));
        attr_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue"));
        attrs_agg.subAggregation(attr_agg);
        source.aggregation(attrs_agg);
        SearchRequest searchRequest = new SearchRequest(new String[]{ESConstant.PRODUCT_INDEX}, source);

        log.info("dsl -> {}", searchRequest.source());
        return searchRequest;
    }
}
